package com.mxx.common.payment.wechat;

import com.mxx.common.conv.JsonUtil;
import com.mxx.common.payment.*;
import com.mxx.common.conv.JacksonConverter;
import com.mxx.common.exception.CommentException;
import com.mxx.common.http.OKHttpUtil;
import com.mxx.common.http.OkHttpResponse;
import com.mxx.common.security.AesCipher;
import com.mxx.common.utils.*;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.TlsVersion;
import org.springframework.stereotype.Service;

import javax.net.ssl.SSLContext;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Slf4j
@Service("weChatService")
public class DefaultWeChatPaymentHandler implements PaymentHandler<WeChatPrepaymentResponse> {

    private WeChatPaymentParameter paymentParameter;

    private SSLContext sslContext;

    private JacksonConverter jacksonConverter;

    public DefaultWeChatPaymentHandler(WeChatPaymentParameter paymentParameter, JacksonConverter jacksonConverter) {
        this.paymentParameter = paymentParameter;
        this.jacksonConverter = jacksonConverter;
        sslContext = paymentParameter.loadSSLContext();
    }

    @Override
    public WeChatPrepaymentResponse prepaymentProcess(TradeMark tradeMark) {
        String nonceStr = RandomUtil.randomCode(16);
        WeChatRequest entity = new WeChatRequest();
        entity.setAppid(paymentParameter.getAppId())
                .setMch_id(paymentParameter.getMchId())
                .setNonce_str(nonceStr)// 随机字符串
                .setBody(tradeMark.getTitle()) //标题 (商品描述)
                .setOut_trade_no(tradeMark.getTradeId())// 订单编号
                .setTotal_fee(new BigDecimal(tradeMark.getAmount()).multiply(BigDecimal.valueOf(100)).intValue() + "")// 订单总金额，单位为分
                .setSpbill_create_ip(tradeMark.getIp())// 调用微信支付API的机器IP
                .setNotify_url(paymentParameter.getPaymentNotifyUrl())
                .setTrade_type("APP");// 支付类型 APP

        Map entityJSON = jacksonConverter.convertValue(entity, HashMap.class);
        String sign = Maps.ascii(entityJSON);
        sign = String.format("%s&key=%s", sign, paymentParameter.getKey());
        entity.setSign(SignUtil.toMD5(sign).toUpperCase());

        String xmlData = XmlUtil.toXml(entity);
        OkHttpResponse responseEntity = OKHttpUtil.doPost(paymentParameter.getPrepaymentUrl(), xmlData);
        Map<String, String> responseMap = XmlUtil.doXMLParse(responseEntity.getBody());
        log.info("preRequest WeChat result msg: {}", JsonUtil.toJSONString(responseMap));
        if ("SUCCESS".equals(responseMap.get("result_code"))) {
            return WeChatPrepaymentResponse.create(responseMap.get("prepay_id"), tradeMark.getTradeId(), paymentParameter);
        } else {
            throw new CommentException("%s", responseMap.get("return_msg"));
        }
    }


    @Override
    public void paymentFallback(FinishTradeHandler finishTradeHandler, Map<String, String> params) {
        PayResponse payResponse = null;

        Map<String, String> resultModel = XmlUtil.doXMLParse(params.get("notifyData"));
        if ("SUCCESS".equals(resultModel.get("return_code"))) {
            String sign = resultModel.remove("sign").toString();
            resultModel.put("appid", paymentParameter.getAppId());
            resultModel.put("mch_id", paymentParameter.getMchId());
            String newSign = Maps.ascii(resultModel);
            newSign = String.format("%s&key=%s", newSign, paymentParameter.getKey());

            if (sign.equalsIgnoreCase(SignUtil.toMD5(newSign))) {// 验签
                payResponse = PayResponse.createWeChatQueryAndNotifyResponse(resultModel);
            }
        }
        if (payResponse == null) {
            return;
        }
        String tradeNo = payResponse.getOutTradeNo();

        if ("FAIL".equals(payResponse.getResultCode())) {// 支付失败
            finishTradeHandler.paymentFailure(TradeType.WeChat, tradeNo, payResponse.getTransactionId(), payResponse.getFailCode(), payResponse.getFailMsg());
        } else if ("SUCCESS".equals(payResponse.getResultCode())) {// 支付成功
            finishTradeHandler.paymentSuccessfully(TradeType.WeChat, tradeNo, payResponse.getTransactionId());
        } else { // 其他状态
            log.info("WeChat perOrder resultCode：[{}]", payResponse.getResultCode());
            return;
        }
        return;
    }

    @Override
    public PayResponse close(String orderNo) {
        Map<String, String> entity = new HashMap<>();
        entity.put("appid", paymentParameter.getAppId());
        entity.put("mch_id", paymentParameter.getMchId());
        entity.put("out_trade_no", orderNo);
        entity.put("nonce_str", RandomUtil.randomCode(16));
        String sign = Maps.ascii(entity);
        sign = String.format("%s&key=%s", sign, paymentParameter.getKey());
        entity.put("sign", SignUtil.toMD5(sign));
        String xmlData = XmlUtil.toXml(entity);

        OkHttpResponse responseEntity = OKHttpUtil.doPost(paymentParameter.getClosedUrl(), xmlData);
        Map map = XmlUtil.doXMLParse(responseEntity.getBody());
        return PayResponse.createWeChatCloseResponse(map);

    }


    @Override
    public void refund(FinishTradeHandler finishTradeHandler, String outTradeNo, String totalAmount, String refundAmount, String refundReason) {
        Map<String, String> entity = new HashMap<>();
        entity.put("appid", paymentParameter.getAppId());
        entity.put("mch_id", paymentParameter.getMchId());
        entity.put("nonce_str", RandomUtil.randomCode(16));
        entity.put("out_trade_no", outTradeNo);// 商户订单号
        entity.put("out_refund_no", outTradeNo);// 商户退款单号
        entity.put("total_fee", new BigDecimal(totalAmount).multiply(BigDecimal.valueOf(100)).intValue() + "");// 订单金额
        entity.put("refund_fee", new BigDecimal(refundAmount).multiply(BigDecimal.valueOf(100)).intValue() + "");// 退款金额
        entity.put("notify_url", paymentParameter.getRefundNotifyUrl());
        String sign = Maps.ascii(entity);
        sign = String.format("%s&key=%s", sign, paymentParameter.getKey());
        entity.put("sign", SignUtil.toMD5(sign));
        String xmlData = XmlUtil.toXml(entity);

        Request request = new Request.Builder()
                .url(paymentParameter.getRefundUrl())
                .post(RequestBody.create(OKHttpUtil.JSON_TYPE, xmlData))
                .build();
        if (sslContext == null) {
            throw new RuntimeException("WeChatPay refund SSL cert setup fail!");
        }
        OkHttpResponse responseEntity = OKHttpUtil.doCallSSL(request, sslContext, TlsVersion.TLS_1_2);
        log.info(responseEntity.getBody());

        Map<String, String> resultModel = XmlUtil.doXMLParse(responseEntity.getBody());
        PayResponse payResponse = PayResponse.createWeChatRefundResponse(resultModel);
        if ("SUCCESS".equals(payResponse.getRefundStatus())) {
            finishTradeHandler.refundSuccessfully(TradeType.WeChat, outTradeNo, payResponse.getTransactionId(), refundReason);
        } else {
            finishTradeHandler.refundFailure(TradeType.WeChat, outTradeNo, payResponse.getTransactionId(), payResponse.getFailCode(), payResponse.getFailMsg());
        }
    }

    @Override
    public boolean analysisOfRefund(FinishTradeHandler paymentHandler, Map<String, String> params) {
        PayResponse payResponse = null;

        Map<String, String> resultModel = XmlUtil.doXMLParse(params.get("notifyData"));
        if ("SUCCESS".equals(resultModel.get("return_code"))) {
            byte[] reqInfo = Base64.getDecoder().decode(resultModel.get("req_info"));

            Optional<byte[]> decryptOptional = AesCipher.ecb(SignUtil.toMD5(paymentParameter.getKey()).toLowerCase())
                    .padding().decryptToByte(reqInfo);
            if (!decryptOptional.isPresent()) {
                log.error("WeChatPaymentServiceImpl analysisOfRefund ASE decrypt fail!");
                return false;
            }
            resultModel = XmlUtil.doXMLParse(new String(decryptOptional.get(), StandardCharsets.UTF_8));

            log.info("WeChatPaymentServiceImpl analysisOfRefund msg：{}", params);
            payResponse = PayResponse.createWeChatRefundQueryAndNotifyResponse(resultModel);
        }
        if (payResponse == null) {
            return false;
        }

        String tradeNo = payResponse.getOutRequestNo();
        if ("SUCCESS".equals(payResponse.getRefundStatus())) {
            paymentHandler.refundSuccessfully(TradeType.WeChat, tradeNo, payResponse.getTransactionId(), null);
        } else if ("REFUNDCLOSE".equals(payResponse.getRefundStatus())) { // 退款关闭
            log.warn("WeChatPay 退款已关闭");
        } else if ("PROCESSING".equals(payResponse.getRefundStatus())) { // 退款处理中
            return false;
        } else {// 退款异常
            log.error("WeChatPay 退款异常");
            return false;
        }
        return true;
    }

    @Override
    public void selectPayment(FinishTradeHandler finishTradeHandler, String outTradeNo) {
        Map<String, String> entity = new HashMap<>();
        entity.put("appid", paymentParameter.getAppId());
        entity.put("mch_id", paymentParameter.getMchId());
        entity.put("out_trade_no", outTradeNo);
        entity.put("nonce_str", RandomUtil.randomCode(16));
        String sign = Maps.ascii(entity);
        sign = String.format("%s&key=%s", sign, paymentParameter.getKey());
        entity.put("sign", SignUtil.toMD5(sign));
        String xmlData = XmlUtil.toXml(entity);

        OkHttpResponse responseEntity = OKHttpUtil.doPost(paymentParameter.getSelectUrl(), xmlData);
        Map resultModel = XmlUtil.doXMLParse(responseEntity.getBody());
        PayResponse payResponse = PayResponse.createWeChatQueryAndNotifyResponse(resultModel);
        if ("SUCCESS".equals(payResponse.getResultCode())) {
            finishTradeHandler.paymentSuccessfully(TradeType.WeChat, outTradeNo, payResponse.getTransactionId());
        } else {
            finishTradeHandler.paymentFailure(TradeType.WeChat, outTradeNo, payResponse.getTransactionId(), payResponse.getFailCode(), payResponse.getFailMsg());
        }
    }

    @Override
    public PayResponse selectOfRefund(String outTradeNo, String operatorId) {
        Map<String, String> entity = new HashMap<>();
        entity.put("appid", paymentParameter.getAppId());
        entity.put("mch_id", paymentParameter.getMchId());
        // entity.put("out_trade_no", outTradeNo);
        entity.put("out_refund_no", operatorId);
        entity.put("nonce_str", RandomUtil.randomCode(16));
        String sign = Maps.ascii(entity);
        sign = String.format("%s&key=%s", sign, paymentParameter.getKey());
        entity.put("sign", SignUtil.toMD5(sign));
        String xmlData = XmlUtil.toXml(entity);

        OkHttpResponse responseEntity = OKHttpUtil.doPost(paymentParameter.getRefundQueryUrl(), xmlData);
        Map map = XmlUtil.doXMLParse(responseEntity.getBody());

        if ("SUCCESS".equals(map.get("result_code"))) {
            return PayResponse.createWeChatRefundQueryAndNotifyResponse(map);
        } else {
            return null;
        }
    }

    @Override
    public TradeType getTradeType() {
        return TradeType.WeChat;
    }
}
